/*
 * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved.
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contact: mosaic@fokus.fraunhofer.de
 */
package org.eclipse.mosaic.fed.sumo.traci.commands;

import com.google.common.collect.Lists;
import org.eclipse.mosaic.fed.sumo.traci.*;
import org.eclipse.mosaic.fed.sumo.traci.complex.Status;
import org.eclipse.mosaic.fed.sumo.traci.constants.CommandVariableSubscriptions;
import org.eclipse.mosaic.fed.sumo.traci.constants.SumoVar;
import org.eclipse.mosaic.fed.sumo.traci.constants.TraciDatatypes;
import org.eclipse.mosaic.rti.TIME;
import org.eclipse.mosaic.rti.api.InternalFederateException;

import java.util.Collection;
import java.util.function.Predicate;

import static org.eclipse.mosaic.fed.sumo.traci.constants.CommandRetrieveVehicleTypeState.*;

public class VehicleTypeSubscribe extends AbstractTraciCommand<Void> {

    private static Collection<SumoVar> getSubscriptionCodes() {
        return Lists.newArrayList(VAR_HEIGHT, VAR_LENGTH, VAR_WIDTH);
    }

    /**
     * Create a new {@link VehicleTypeSubscribe}
     * Access needs to be public, because command is called using Reflection.
     *
     * @param traciConnection Connection to Traci
     * @see <a href="https://sumo.dlr.de/docs/TraCI/VehicleType_Value_Retrieval.html">Variable Subscription</a>
     */
    public VehicleTypeSubscribe(TraciConnection traciConnection) {
        this(traciConnection, getSubscriptionCodes());
    }

    /**
     * Creates a new {@link VehicleSubscribe} object
     * Access needs to be public, because command is called using Reflection.
     *
     * @param traciConnection    Connection to Traci
     * @param subscriptionCodes  The parameters for an applicable configuration
     */
    public VehicleTypeSubscribe(TraciConnection traciConnection, Collection<SumoVar> subscriptionCodes) {
        super(TraciVersion.LOWEST);

        final SumoVersion currentVersion = traciConnection.getCurrentVersion();
        final Predicate<SumoVar> isSubscriptionVarApplicable =
                subscriptionVar -> subscriptionVar.isAvailable(currentVersion.getTraciVersion());

        final int subscriptionSize = (int) subscriptionCodes.stream().filter(isSubscriptionVarApplicable).count();

        TraciCommandWriterBuilder write = write()
                .command(CommandVariableSubscriptions.COMMAND_SUBSCRIBE_VEHICLE_TYPE_VALUES)
                .writeDoubleParam() // start time
                .writeDoubleParam() // end time
                .writeVehicleIdParam()
                .writeByte(subscriptionSize);

        subscriptionCodes.stream()
                .filter(isSubscriptionVarApplicable)
                .forEach(subscriptionVar -> {

                    write.writeByte(subscriptionVar.var);

                    if (subscriptionVar instanceof SumoVar.WithParam) {
                        write.writeByte(TraciDatatypes.DOUBLE);
                        write.writeDouble(((SumoVar.WithParam) subscriptionVar).getValue());
                    }
                });

        read()
                .expectByte(CommandVariableSubscriptions.RESPONSE_SUBSCRIBE_VEHICLE_TYPE_VALUES)
                .skipString()
                .expectByte(subscriptionSize)
                .skipRemaining();
    }

    /**
     * This method executes the command with the given arguments in order to subscribe the vehicle to the application.
     *
     * @param traciCon  Connection to Traci.
     * @param vehicleId The Id of the Vehicle.
     * @param startTime The time to subscribe the vehicle.
     * @param endTime   The end time of the subscription of the vehicle in the application.
     * @throws TraciCommandException     if the status code of the response is ERROR. The TraCI connection is still available.
     * @throws InternalFederateException if some serious error occurs during writing or reading. The TraCI connection is shut down.
     */
    public void execute(TraciConnection traciCon, String vehicleId, long startTime, long endTime) throws TraciCommandException, InternalFederateException {
        super.execute(traciCon, ((double) startTime) / TIME.SECOND, ((double) endTime) / TIME.SECOND, vehicleId);
    }

    @Override
    protected Void constructResult(Status status, Object... objects) {
        return null;
    }
}
